/*******************************************************************************
 * Copyright 2010 Simon Mieth
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package org.kabeja.batik.tools;

import java.awt.Color;
import java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.Iterator;
import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;

import org.apache.batik.transcoder.Transcoder;
import org.apache.batik.transcoder.TranscoderException;
import org.apache.batik.transcoder.TranscoderInput;
import org.apache.batik.transcoder.TranscoderOutput;
import org.apache.batik.transcoder.TranscodingHints.Key;
import org.apache.batik.transcoder.image.ImageTranscoder;
import org.kabeja.xml.AbstractSAXFilter;
import org.kabeja.xml.SAXSerializer;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;


public abstract class AbstractSAXSerializer extends AbstractSAXFilter
    implements SAXSerializer {
    public static final String TRANSCODER_HINT_TYPE_COLOR = "color";
	public static final String TRANSCODER_HINT_TYPE_INTEGER = "integer";
	public static final String TRANSCODER_HINT_TYPE_FLOAT = "float";
	public static final String SUFFIX_JPEG = "jpg";
    public static final String SUFFIX_PNG = "png";
    public static final String SUFFIX_TIFF = "tif";
    public static final String SUFFIX_PDF = "pdf";
    public static final String MIME_TYPE_JPEG = "image/jepg";
    public static final String MIME_TYPE_PNG = "image/png";
    public static final String MIME_TYPE_TIFF = "image/tiff";
    public static final String MIME_TYPE_PDF = "application/pdf";
    public static final String TRANSCODER_HINT_TYPE_SUFFIX = "_type";
    public static final String TRANSCODER_HINT_PREFIX = "KEY";

    /**
     * Allows you to setup a width in px,inch,mm,cm,pt. This property overrides the PAPER_PROPERTY.
     */
    public static final String PROPERTY_WIDTH = "width";

    /**
     * Allows you to setup a height in px,inch,mm,cm,pt. This property overrides the PAPER_PROPERTY.
     */
    public static final String PROPERTY_HEIGHT = "height";

    /**
     * Allows you to setup the dpi
     */
    public static final String PROPERTY_DPI = "dpi";
    public static final String PROPERTY_QUALITY = "quality";

    /**
     * You can choose A0-A6 papers and Letter.
     */
    public static final String PROPERTY_PAPER = "paper";

    /**
     * Lets you choose the orientation of a paper (landscape).
     */
    public static final String PROPERTY_ORIENTATION = "orientation";
    public static final float INCH_TO_MM = 25.4f;
    public static final float PT_TO_MM = 0.3527777777777f;
    public int DPI = 96;
    public float PIXEL_UNIT_TO_MM = INCH_TO_MM / DPI;
    protected OutputStream out;
    protected float width;
    protected float height;
    protected double quality = 0.9;
    protected Transcoder transcoder;
    protected boolean scaleToFit = true;
    protected Document document;

    protected abstract Transcoder createTranscoder();

    /*
     * (non-Javadoc)
     *
     * @see org.kabeja.xml.SAXSerializer#setProperties(java.util.Map)
     */
    public void setProperties(Map properties) {
        if (properties.containsKey(PROPERTY_DPI)) {
            this.DPI = Integer.parseInt((String) properties.get(PROPERTY_DPI));
            this.PIXEL_UNIT_TO_MM = INCH_TO_MM / this.DPI;
        }

        if (properties.containsKey(PROPERTY_PAPER)) {
            this.parsePaper(((String) properties.get(PROPERTY_PAPER)).toLowerCase());
        }

        if (properties.containsKey(PROPERTY_QUALITY)) {
            this.quality = Double.parseDouble((String) properties.get(
                        PROPERTY_QUALITY));
        }

        if (properties.containsKey(PROPERTY_WIDTH)) {
            this.width = unitsToPixel(((String) properties.get(PROPERTY_WIDTH)).trim());
        }

        if (properties.containsKey(PROPERTY_HEIGHT)) {
            this.height = unitsToPixel(((String) properties.get(PROPERTY_HEIGHT)).trim());
        }

        if (properties.containsKey(PROPERTY_ORIENTATION)) {
            String orientation = ((String) properties.get(PROPERTY_ORIENTATION)).toLowerCase();

            if (orientation.equals("landscape")) {
                float w = this.width;
                this.width = this.height;
                this.height = w;
            }
        }
        this.properties = properties;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.kabeja.xml.SAXSerializer#setOutput(java.io.OutputStream)
     */
    public void setOutput(OutputStream out) {
        this.out = out;
    }

    /*
     * (non-Javadoc)
     *
     * @see org.xml.sax.ContentHandler#endDocument()
     */
    public void endDocument() throws SAXException {
        super.endDocument();

        try {
            transcoder = createTranscoder();
            setupTranscoder(transcoder);

            TranscoderInput transInput = new TranscoderInput(this.document);

            // Buffering is done by the pipeline (See shouldSetContentLength)
            TranscoderOutput transOutput = new TranscoderOutput(this.out);
            setupTranscoder(transcoder);

            transcode(transInput, transOutput);
        } catch (TranscoderException e) {
            throw new SAXException(e);
        }
    }

    /**
     * Override this if your Transcoder support more
     * options.
     * @param t
     */
    protected void setupTranscoder(Transcoder t) {

        
        //setup the transcoder hints
        Iterator i = this.properties.keySet().iterator();
        while(i.hasNext()){
        	String key = (String)i.next();
        	
			if(key.startsWith(TRANSCODER_HINT_PREFIX)){
        		Field[] fields = transcoder.getClass().getFields();
        		for(int x=0;x<fields.length;x++){
        			if(fields[x].getName().equals(key)){
        				try {
							Key k = (Key)fields[x].get(null);
							
							String type = (String)this.properties.get((key+TRANSCODER_HINT_TYPE_SUFFIX));
							Object value=null;
							if(TRANSCODER_HINT_TYPE_FLOAT.equals(type)){
								value = new Float(Float.parseFloat((String)this.properties.get(key)));
							}else if(TRANSCODER_HINT_TYPE_INTEGER.equals(type)){
								value = new Integer(Integer.parseInt((String)this.properties.get(key)));
							}else if(TRANSCODER_HINT_TYPE_COLOR.equals(type)){
								
							    value = Color.decode((String)this.properties.get(key));
							
							}else{
								value = (String)this.properties.get(key);
							}
							transcoder.addTranscodingHint(k,value);
						} catch (Exception e) {
							
							e.printStackTrace();
						} 
        				
        			}
        		}
        		
        	}
        }
        
        transcoder.addTranscodingHint(ImageTranscoder.KEY_PIXEL_UNIT_TO_MILLIMETER,
                new Float(this.PIXEL_UNIT_TO_MM));

            if (this.width > 0.0) {
                transcoder.addTranscodingHint(ImageTranscoder.KEY_WIDTH,
                    new Float(this.width));
            }

            if (this.height > 0.0) {
                transcoder.addTranscodingHint(ImageTranscoder.KEY_HEIGHT,
                    new Float(this.height));
            }
    }

    /*
     * (non-Javadoc)
     *
     * @see org.xml.sax.ContentHandler#startDocument()
     */
    public void startDocument() throws SAXException {
        try {
            SAXTransformerFactory factory = (SAXTransformerFactory) SAXTransformerFactory.newInstance();
            TransformerHandler f = factory.newTransformerHandler();
            this.document = DocumentBuilderFactory.newInstance()
                                                  .newDocumentBuilder()
                                                  .newDocument();

            // put the the transformer in the chain
            f.setResult(new DOMResult(document));

            super.setContentHandler(f);
        } catch (TransformerConfigurationException e) {
            throw new SAXException(e);
        } catch (IllegalArgumentException e) {
            throw new SAXException(e);
        } catch (TransformerFactoryConfigurationError e) {
            throw new SAXException(e.getException());
        } catch (ParserConfigurationException e) {
            throw new SAXException(e);
        }

        super.startDocument();
    }

    protected void transcode(TranscoderInput input, TranscoderOutput output)
        throws TranscoderException {
        transcoder.transcode(input, output);
    }

    protected float unitsToPixel(String size) {
        if (size.endsWith("px")) {
            return Float.parseFloat(size.substring(0, size.length() - 2));
        } else if (size.endsWith("in")) {
            return (Float.parseFloat(size.substring(0, size.length() - 2)) * INCH_TO_MM) / PIXEL_UNIT_TO_MM;
        } else if (size.endsWith("pt")) {
            return (Float.parseFloat(size.substring(0, size.length() - 2)) * PT_TO_MM) / PIXEL_UNIT_TO_MM;
        } else if (size.endsWith("cm")) {
            float units = Float.parseFloat(size.substring(0, size.length() - 2));
            float pixel = (float) ((units * 100) / PIXEL_UNIT_TO_MM);

            return pixel;
        } else if (size.endsWith("mm")) {
            float units = Float.parseFloat(size.substring(0, size.length() - 2));
            float pixel = (float) (units / PIXEL_UNIT_TO_MM);

            return pixel;
        } else if (size.endsWith("m")) {
            float units = Float.parseFloat(size.substring(0, size.length() - 1));
            float pixel = (float) ((units * 1000) / PIXEL_UNIT_TO_MM);

            return pixel;
        } else {
            return Float.parseFloat(size);
        }
    }

    protected void parsePaper(String paper) {
        if (paper.equals("a0")) {
            this.width = 841 / PIXEL_UNIT_TO_MM;
            this.height = 1189 / PIXEL_UNIT_TO_MM;
        } else if (paper.equals("a1")) {
            this.width = 594 / PIXEL_UNIT_TO_MM;
            this.height = 841 / PIXEL_UNIT_TO_MM;
        } else if (paper.equals("a2")) {
            this.width = 420 / PIXEL_UNIT_TO_MM;
            this.height = 594 / PIXEL_UNIT_TO_MM;
        } else if (paper.equals("a3")) {
            this.width = 297 / PIXEL_UNIT_TO_MM;
            this.height = 420 / PIXEL_UNIT_TO_MM;
        } else if (paper.equals("a4")) {
            this.width = 210 / PIXEL_UNIT_TO_MM;
            this.height = 297 / PIXEL_UNIT_TO_MM;
        } else if (paper.equals("a5")) {
            this.width = 148 / PIXEL_UNIT_TO_MM;
            this.height = 210 / PIXEL_UNIT_TO_MM;
        } else if (paper.equals("a6")) {
            this.width = 105 / PIXEL_UNIT_TO_MM;
            this.height = 148 / PIXEL_UNIT_TO_MM;
        } else if (paper.equals("letter")) {
            this.width = 216 / PIXEL_UNIT_TO_MM;
            this.height = 279 / PIXEL_UNIT_TO_MM;
        }

        //add more papers here
    }

    public Map getProperties() {
        return this.properties;
    }
}
